package gaileenchess;

import java.util.LinkedList;

public class Board
{
    private Piece[][] pos; // the board configuration itself. A null piece means an empty square
    private Player[] players;
    private int numMoves=0;
    
    public Board()
    {
        this.pos = new Piece[8][8]; // chess board is always 8x8
    }
    
    // test constructor
    public Board(Piece[][] p)
    {
        this.pos = p;
    }
    
    public void addPlayers(Player[] p)
    {
        this.players = p;
    }
    
    // returns true if the piece you're adding replaces another piece
    private boolean addPiece(Piece p)
    {
        int row = p.getRow();
        int column = p.getCol();
        boolean ret = (pos[row][column] != null);
        pos[row][column] = p;
        return ret;
    }
    
    public static boolean addPiece(Piece[][] board, Piece p)
    {
        int row = p.getRow();
        int column = p.getCol();
        boolean ret = (board[row][column] != null);
        board[row][column] = p;
        return ret;
    }
    
    /*
    private Piece findKing(Common.Side side)
    {
        boolean found = false;
        Piece king = null;
        
        for(int x = 0; x < 8; x++)
        {
            for(int y = 0; y < 8; y++)
            {
                if(pos[x][y] != null && pos[x][y].getType() == Piece.Type.KING && pos[x][y].getSide() == side)
                {
                    if(found)
                    {
                        return null;
                    }
                    else
                    {
                        king = pos[x][y];
                        found = true;
                    }
                }
            }
        }
        
        return king;
    }
    */
    
    private Piece findKing(int player)
    {
        boolean found = false;
        Piece king = null;

        for(Piece p : this.players[player].getPieceList())
        {
            if(p.getType() == Piece.Type.KING)
            {
                if(found)
                {
                    return null;
                }
                else
                {
                    king = p;
                    found = true;
                }
            }
        }

        return king;
    }
    
    private boolean checkPawns()
    {
        int x = -1;
        
        for(int i = 0; i < 2; i++)
        {
            switch(i)
            {
                case 0:
                x = 0;
                break;
                
                case 1:
                x = 7;
                break;
                
                default:
                x = -1;
            }
            
            for(int y = 0; y < 8; y++)
            {
                if(pos[x][y] != null && pos[x][y].getType() == Piece.Type.PAWN)
                {
                    return false;
                }
            }
        }
        
        return true;
    }
    
    public boolean isValid()
    {
        boolean ret = true;
        
        Piece whiteKing = findKing(0);
        Piece blackKing = findKing(1);
        
        ret &= (whiteKing != null && blackKing != null);
        ret &= checkPawns();
        
        // if above were false, things in here could cause null pointers
        if(ret)
        {
            ret &= !isInCheck(0) & !isInCheck(1);

            //stalemate
            ret &= !isGameOver(0, false) & !isGameOver(1, false);
        }
        
        return ret;
    }
    
    public void reset()
    {
        this.pos = new Piece[8][8]; // clear board
        
        // hard-coded configuration of a standard chess board
        addPiece(new Rook(Common.Side.WHITE, 0, 0));
        addPiece(new Knight(Common.Side.WHITE, 0, 1));
        addPiece(new Bishop(Common.Side.WHITE, 0, 2));
        addPiece(new Queen(Common.Side.WHITE, 0, 3));
        addPiece(new King(Common.Side.WHITE, 0, 4));
        addPiece(new Bishop(Common.Side.WHITE, 0, 5));
        addPiece(new Knight(Common.Side.WHITE, 0, 6));
        addPiece(new Rook(Common.Side.WHITE, 0, 7));
        
        addPiece(new Rook(Common.Side.BLACK, 7, 0));
        addPiece(new Knight(Common.Side.BLACK, 7, 1));
        addPiece(new Bishop(Common.Side.BLACK, 7, 2));
        addPiece(new Queen(Common.Side.BLACK, 7, 3));
        addPiece(new King(Common.Side.BLACK, 7, 4));
        addPiece(new Bishop(Common.Side.BLACK, 7, 5));
        addPiece(new Knight(Common.Side.BLACK, 7, 6));
        addPiece(new Rook(Common.Side.BLACK, 7, 7));
        
        for(int x = 0; x < 8; x++)
        {
            addPiece(new Pawn(Common.Side.WHITE, 1, x));
            addPiece(new Pawn(Common.Side.BLACK, 6, x));
        }
    }
    
    public Piece[][] getPos()
    {
        return pos;
    }
    
    // print out current board to the command-line
    public void display()
    {
        System.out.print("  ");
        for(int m=0; m<8; m++)
        {
            System.out.print("  "+(char)(97+m)+"  ");
        }
        System.out.println();
        for(int x = 7; x >= 0; x--) // notice reversed loop, since white players (rows 0-1) are traditionally on the bottom
        {
            System.out.print("  ");
            for(int m=0; m<8; m++)
            {
                System.out.print(" ----");
            }
            System.out.println();
            System.out.print(x+1+" ");
            for(int y = 0; y < 8; y++)
            {
                if(pos[x][y] == null)
                {
                    System.out.print("|    "); // no piece here
                }
                else
                {
                    // print colour and type of piece at this square
                    System.out.print("| "+pos[x][y].getStringSide() + pos[x][y].getStringType() + " ");
                }
            }
            System.out.print("|");
            System.out.println(); // end with an empty line to keep the next text (whatever it is) clean and separate
        }
        System.out.print("  ");
          for(int m=0; m<8; m++)
            {
                System.out.print(" ----");
            }
          System.out.println();
    }
    
    // makes sure a given move is possible and legal, from the piece's perspective, and the game's as a whole
    public boolean isValidMove(int player, Position pos1, Position pos2)
    {
        Common.Side playerSide = players[player].getSide();
        Piece piece = this.pos[pos1.row][pos1.column];
        
        if(piece == null || piece.getSide() != playerSide || isOccupied(pos2, piece.getSide()))
        {
            return false;
        }
        // TODO
        return (this.pos[pos1.row][pos1.column].isValidMove(playerSide, this, pos1, pos2)
            && !willBeInCheck(player, pos1, pos2));//TODO shouldn't check willBeInCheck when this is called from isInCheck or willBeInCheck
    }
    
    
    public boolean isInCheck(int player)
    {
        Position king = players[player].getKing().getPosition(); // player's king's position
        int oppositePlayer = (player + 1) % 2;
        
        for(Piece p : players[oppositePlayer].getPieceList()) // opposite player's pieces
        {
            if(isValidMove(oppositePlayer, p.getPosition(), king))
            {
                // if any of opposite player's pieces can move to king, it's check
                return true;
            }
        }
        
        return false;
    }
    
    public boolean willBeInCheck(int player, Board.Position pos1, Board.Position pos2)
    {
        boolean ret = false;
        Piece taken = pos[pos2.row][pos2.column];
        
        move(pos1, pos2, false, player);
        ret = isInCheck(player);
        move(pos2, pos1, false, player);
        
        pos[pos2.row][pos2.column] = taken;
        
        return ret;
    }
    
    // move a piece from pos1 to pos2. This function assumes isValidMove() was already called to make sure
    //  this move is possible and legal, since this function doesn't actually check anything itself
    public void move(Position pos1, Position pos2, boolean countMove, int player)
    {
        if(countMove)
        {
            if(this.pos[pos2.row][pos2.column] != null || this.pos[pos1.row][pos1.column].type == Piece.Type.PAWN)
            {
                numMoves=0;
            }
            else
            {
                numMoves++;
            }
        }
        
        this.pos[pos1.row][pos1.column].move(pos2, countMove);
        this.pos[pos2.row][pos2.column] = this.pos[pos1.row][pos1.column];
        this.pos[pos1.row][pos1.column] = null;
        
        Piece old = this.pos[pos2.row][pos2.column];
                
        if(old.type==Piece.Type.PAWN && (pos2.row == 0 || pos2.row==7) && countMove)
        {
            Piece.Type t =  players[player].promote();
            LinkedList<Piece> pieceList = players[player].getPieceList();
            switch(t)
            {
                case QUEEN:
                    this.pos[pos2.row][pos2.column]=new Queen(old.side, pos2.row, pos2.column);
                    pieceList.remove(new Pawn(players[player].getSide(),pos2.row, pos2.column));
                    pieceList.add(new Queen(players[player].getSide(), pos2.row, pos2.column));
                    players[player].setPieceList(pieceList);
                    break;
                case ROOK:
                    this.pos[pos2.row][pos2.column]=new Rook(old.side, pos2.row, pos2.column);
                    break;
                case BISHOP:
                    this.pos[pos2.row][pos2.column]=new Bishop(old.side, pos2.row, pos2.column);
                    break;
                case KNIGHT:
                    this.pos[pos2.row][pos2.column]=new Knight(old.side, pos2.row, pos2.column);
                    break;
            }
            
        }
    }
    
    public boolean isOccupied(Position p)
    {
        return (this.pos[p.row][p.column] != null);
    }
    
    public boolean isOccupied(Position p, Common.Side side)
    {
        return (this.pos[p.row][p.column] != null && this.pos[p.row][p.column].getSide() == side);
    }
    
    public boolean isPathBlocked(Position pos1, Position pos2)
    {
        int r = pos1.row;
        int c = pos1.column;
        
        if(r > pos2.row)
        {
            r--;
        }
        else if(r < pos2.row)
        {
            r++;
        }
        
        if(c > pos2.column)
        {
            c--;
        }
        else if(c < pos2.column)
        {
            c++;
        }
        
        while(r != pos2.row || c != pos2.column)
        {
            if(this.pos[r][c] != null)
            {
                return true;
            }
            
            if(r > pos2.row)
            {
                r--;
            }
            else if(r < pos2.row)
            {
                r++;
            }
            
            if(c > pos2.column)
            {
                c--;
            }
            else if(c < pos2.column)
            {
                c++;
            }
        }
        
        return false;
    }
    
    public boolean isGameOver(int player, boolean check)
    {
        return (this.players[player].getValidMoves(check, player).size() == 0);
    }
    
    public Piece getPiece(Position p)
    {
        return this.pos[p.row][p.column];
    }
    
    
    
    // a small, simple (nested) class for a coordinate position on the board
    public static class Position
    {
        public final int row;
        public final int column;
        
        
        public Position(int r, int c)
        {
            this.row = r;
            this.column = c;
        }
        
        public Position diff(Position p)
        {
            return new Position(this.row - p.row, this.column - p.column);
        }
        
        public boolean isValid()
        {
            return (this.row >= 0 && this.row < 8 && this.column >= 0 && this.column < 8);
        }
        
        public Position add(Position p)
        {
            return new Position(this.row + p.row, this.column + p.column);
        }
        
        public String toString()
        {
            return ((char)(this.column + 65) + "" + (char)(this.row + 49));
        }
        
        
        // even smaller, simpler nested class mostly for returning lists of moves
        public static class Move
        {
            public final Position p;
            public final Position q;
            
            public Move(Position p1, Position p2)
            {
                this.p = p1;
                this.q = p2;
            }
            
            public String toString()
            {
                return (this.p.toString() + " " + this.q.toString());
            }
            
            public String destToString()
            {
                return this.q.toString();
            }
        }
    }
}
